Create automations runtimes and frequencies dashboard using REST API
Have you ever wanted to schedule a new automation, and asked yourself: When should it run?
As your account keeps developing, it becomes harder to remember and track all automations’ runtimes. This could be problematic if you have a lot of automations that use the same data extensions. In fact, you might have already run into this issue without knowing what was causing it.
Having two or more automations run at the same time can cause activities to error randomly.
In this article, I will be presenting a way to create a complementary dashboard to monitor active automations’ runtimes and their frequencies.
PART I: Authentification
This part has already been covered by other SFMC folks out there, so if you are not familiar with API authentication in Marketing Cloud, I suggest you check this article by Zuzanna first.
However, to reinforce the security of our CloudPages, we need to avoid having our API credentials exposed for all SFMC users. In fact, even though the CloudPage’s code is executed on the server, every SFMC user that have permissions to view CloudPages code can have access to this critical data. I’ll let you check this article by Charlie Fay for more details.
The code should look like:
%%[
SET @MID = '50000XXXX'
SET @apiCreds = LOOKUP('REST_Credentials', 'apiCreds', 'MID', @MID)
SET @apiCreds = DecryptSymmetric(@apiCreds, 'aes', 'INT_PWD', @null, 'INT_SALT', @null, 'INT_IV', @null)
]%%
<script runat="server">
Platform.Load("Core","1.1.1");
var apiCreds = Variable.GetValue('@apiCreds');
var credentialObj = Platform.Function.ParseJSON(apiCreds);
var clientId = credentialObj.clientId;
var clientSecret = credentialObj.clientSecret;
var authURL = credentialObj.authURL;
var tenantURL = authURL.substring(0,36);
/* Authentification */
var payload = {
client_id: clientId,
client_secret: clientSecret,
grant_type: "client_credentials"
};
var url = authURL + '/v2/token';
var contentType = 'application/json';
var accessToken;
try {
var accessTokenRequest = HTTP.Post(url, contentType, Stringify(payload));
if(accessTokenRequest.StatusCode == 200) {
var tokenResponse = Platform.Function.ParseJSON(accessTokenRequest.Response[0]);
accessToken = tokenResponse.access_token;
}
} catch (error) {
Write(Stringify(error));
}
PART II: Automation’s REST undocumented endpoints
Get list of all automations of a Business Unit
As far as I know, there is not an official and documented way to get the list of all automations in a Business Unit through the API. Normally, we need to use SOAP to interact with Automation Studio, but it doesn’t offer the kind of data needed in this use case. The same thing applies to the Automation object in SSJS or WSProxy. The only option we have left here is to check Marketing Cloud’s UI.
We need to know what are the calls that are made internally by Marketing Cloud to get the automations list in the Overview tab in Automation Studio. Let’s go ahead and open Automation Studio and click on Inspect Element then select the Network tab.
We will find multiple calls, but the one of interest for us is:
/legacy/v1/beta/automations/automation/definition/
It is a REST beta endpoint. Hence, it is undocumented, so you need to use it with caution and on your own responsibility.
We need to make a REST call to this endpoint using the accessToken
we’ve got from the authentication. But before discovering how to make this call, let us see what it does actually return. We will be interested in these two parts:
- entry: a nested array that contains the data we need about automations (including schedules)
- totalResults: this is very important because it shows the total number of results in the array
Why is totalResults
so important? Because the Salesforce Marketing Cloud’s support suggests not using more than 2500 calls per minute with REST API. And because this JSON does not provide data about runtime frequencies and intervals, we need to pull it through a separate REST API call for every automation in the Entry
array. Therefore, if you think your SFMC’s BU has more than 2500 automation, you might consider editing the script to avoid your API calls getting throttled by Marketing Cloud.
{
"startIndex": 0,
"itemsPerPage": 25,
"totalResults": 308,
"entry": [
{
"id": "dV9jX21YOHpNa0tzTGlYNXXXXXXXX",
"key": "AUTOMATION KEY",
"createdDate": "2020-01-16T14:53:53.177",
"name": "AUTOMATION NAME",
"description": "Audience Builder autogenerated automation for Contact Suppress and Delete tables for ClientID 50000XXXX",
"clientId": 50000XXXX,
"status": "Scheduled",
"createdBy": {
"id": "THEID",
"name": "Rachid Mamai",
"email": "rachid.mamai@xxxx.xx"
},
"modifiedDate": "2020-11-26T21:01:27.7",
"modifiedBy": {
"id": "ANOTHERID",
"name": "System"
},
"processes": [
{
"id": "PROCESSID",
"createdDate": "2020-01-16T14:53:53.27",
"name": "Automation Task for Contact Delete DFU activities",
"description": "Automation Task for Contact Delete DFU activities for Contact Delete and Suppress tables",
"sequence": 0,
"status": "Complete",
"workerCounts": [
{
"name": "ELTActivity",
"objectTypeId": 425,
"count": 2,
"status": "Complete"
}
],
"instanceId": "UDB2cVlSYm9iVSINSTANCEID"
}
],
"schedule": "https://tenantid.rest.marketingcloudapis.com/legacy/v1/beta/automations/schedule/id",
"scheduledTime": "2020-11-27T21:00:00",
"startTime": "2020-11-26T21:00:16.757",
"completedTime": "2020-11-26T21:01:27.603",
"lastRunTime": "2020-11-26T21:00:16.757",
"lastRunInstance": "https://tenantid.rest.marketingcloudapis.com/legacy/v1/beta/bulk/automations/automation/instance/id",
"instanceId": "instanceid",
"automationInstance": "https://tenantid.rest.marketingcloudapis.com/legacy/v1/beta/bulk/automations/automation/instance/id",
"isPlatformObject": false,
"notifications": "https://tenantid.rest.marketingcloudapis.com/legacy/v1/beta/hub/notifications/id",
"automationType": "scheduled",
"lastRunStatus": "Complete"
},
{
"id": "AUTOMATION2ID",
"key": "AUTOMATION2NAME",
"createdDate": "2019-02-26T16:03:41.65",
"name": "AUTOMATION2NAME",
"description": "",
"clientId": 50000XXXX,
"status": "Ready",
"createdBy": {
"id": "ANID",
"name": "Rachid Mamai",
"email": "rachid.mamai@xxxxx.xxx"
},
"modifiedDate": "2020-11-26T20:41:34.253",
"modifiedBy": {
"id": "ANOTHERID",
"name": "System"
},
"processes": [
{
"id": "PROCESSID",
"createdDate": "2020-08-18T15:35:10.653",
"name": "",
"description": "",
"sequence": 0,
"status": "Complete",
"workerCounts": [
{
"name": "QueryDefinition",
"objectTypeId": 300,
"count": 1,
"status": "Complete"
}
],
"instanceId": "INSTANCEID"
},
{
"id": "ANOTHERID",
"createdDate": "2020-08-18T15:35:10.653",
"name": "",
"description": "",
"sequence": 1,
"status": "Complete",
"workerCounts": [
{
"name": "QueryDefinition",
"objectTypeId": 300,
"count": 1,
"status": "Complete"
}
],
"instanceId": "INSTANCEID"
},
{
"id": "ANID",
"createdDate": "2020-08-18T15:35:10.653",
"name": "",
"description": "",
"sequence": 2,
"status": "Complete",
"workerCounts": [
{
"name": "ImportDefinition",
"objectTypeId": 43,
"count": 1,
"status": "Complete"
}
],
"instanceId": "ANOTHERINSTANCEID"
},
{
"id": "ANDANOTHERIDWOUAAAH",
"createdDate": "2020-08-18T15:35:10.653",
"name": "",
"description": "",
"sequence": 3,
"status": "Complete",
"workerCounts": [
{
"name": "QueryDefinition",
"objectTypeId": 300,
"count": 1,
"status": "Complete"
}
],
"instanceId": "LIKEALWAYSANOTHERID"
},
{
"id": "ANDANOTHERONE",
"createdDate": "2020-08-18T15:35:10.653",
"name": "",
"description": "",
"sequence": 4,
"status": "Complete",
"workerCounts": [
{
"name": "QueryDefinition",
"objectTypeId": 300,
"count": 1,
"status": "Complete"
}
],
"instanceId": "ANOTHERONE"
}
],
"startTime": "2020-11-26T20:40:13.79",
"completedTime": "2020-11-26T20:41:34.127",
"lastRunTime": "2020-11-26T20:40:13.79",
"lastRunInstance": "https://tenantid.rest.marketingcloudapis.com/legacy/v1/beta/bulk/automations/automation/instance/id",
"instanceId": "instanceid",
"automationInstance": "https://tenantid.rest.marketingcloudapis.com/legacy/v1/beta/bulk/automations/automation/instance/id",
"isPlatformObject": false,
"notifications": "https://tenantid.rest.marketingcloudapis.com/legacy/v1/beta/hub/notifications/id",
"automationType": "scheduled",
"lastRunStatus": "Complete"
}
]
}
We can modify the variable automationsCount
to handle the number of results we want to be pulled. It’s value is assigned to the top
parameter on our endpoint. We can add other parameters like sort
to sort results by lastRunTime for example.
After making the API call, we need to parse the results using ParseJSON
function. Then, we need to loop through entry
elements. In the code below, we are pulling 25 automation and displaying their descriptions. We will update this code in the next chapter.
<script runat="server">
Platform.Load("Core","1.1.1");
/* The number of autoamtions we want to pull */
var automationsCount = 25;
url = tenantURL + ".rest.marketingcloudapis.com/legacy/v1/beta/automations/automation/definition/?$top="+ automationsCount +"&$skip=0&$sort=lastRunTime%20desc";
var headerNames = ["Authorization"];
var headerValues = ["Bearer " + accessToken];
var automations, automationContent, automationDescription;
try {
automations = HTTP.Get(url, headerNames, headerValues);
automationContent = Platform.Function.ParseJSON(Platform.Function.ParseJSON(Stringify(automations)).Content);
// loop through all results
if(automationContent.entry.length >= 1) {
for (i = 0; i < automationContent.entry.length; i++) {
automationDescription = automationContent.entry[i].description;
Write(automationDescription + "</br>");
/* Do other stuff */
}
}
} catch (e) {
e = Stringify(e).replace(/[\n\r]/g, '')
Write(e);
}
</script>
PART III: Parsing schedules
Now, we will be using another undocumented REST API endpoint:
/automation/v1/automations/{automationid}
To have a better understanding of the code presented later on, let’s take a look at the JSON response returned by this endpoint. As we can see, there is a lot of information returned, but we will be focusing on the schedule
object as it contains all the information we need. It returns the startdate
, the icalRecur
which is crucial to construct automation’s recurrences through the day/week, the scheduleStatus
as long as the endDate
. This JSON contains also data about automation steps which can be exploited to enrich the UI.
{
"id": "d057f44c-9333-4154-999c-XXXXXXXX",
"name": "JB_DIXXXXX",
"description": "",
"key": "ea66db19-de12-41be-1dd4-940XXXXXX",
"typeId": 1,
"type": "scheduled",
"statusId": 6,
"status": "Scheduled",
"categoryId": 486,
"lastRunTime": "2020-11-30T12:31:34.97",
"lastRunInstanceId": "338df884-a4b6-421d-a026-740cXXXXXX",
"schedule": {
"id": "bf46c8dd-6ee9-43b4-94b2-5931XXXXX",
"typeId": 2,
"startDate": "2020-07-13T11:30:00",
"endDate": "2079-06-06T00:00:00",
"scheduledTime": "2020-11-30T20:30:00",
"rangeTypeId": 1,
"occurrences": 516277,
"pattern": "01",
"icalRecur": "FREQ=HOURLY;UNTIL=20790606T080000;INTERVAL=1",
"timezoneName": "Romance Standard Time",
"scheduleStatus": "active",
"timezoneId": 7
},
"steps": [
{
"id": "8b64e4e2-bd0d-468a-b1f8-8aaeXXXXXX",
"name": "",
"step": 1,
"activities": [
{
"id": "c4251624-bb73-44fa-8c74-a066XXXXXX",
"name": "JB_DISPOSITIF_EOS_CIBLAGE",
"activityObjectId": "beede9ae-85e9-4aa1-892d-27320fXXXXX",
"objectTypeId": 300,
"displayOrder": 1,
"targetDataExtensions": [
{
"id": "3bb4b708-e5c1-ea11-b83a-b88303XXXXX",
"name": "EOS_CIBLAGE_MDD",
"key": "09B3E99B-7131-4139-BF1A-DFB402XXXXXX",
"description": "",
"rowCount": 863
}
]
}
]
},
{
"id": "73fd99c7-edf3-4b26-9196-8418eXXXXX",
"name": "",
"step": 2,
"activities": [
{
"id": "436cc2b4-4305-42ef-9cd2-8f9214XXXXX",
"name": "DispositXXXXX",
"activityObjectId": "d40bef32-b6e5-4c1c-8246-8602XXXXX",
"objectTypeId": 952,
"displayOrder": 1
}
]
}
]
}
At this time, we are going to construct the endpoint id parameter automatically to pull all automations schedules. Then we will parse the JSON to construct different data we need to show in our table. We start by checking if the status of the automation is active, then split icalRecur to get the schedule’s frequency, interval, and days. Then, depending on the frequency, we need to format the schedule string differently.
Afterward, we need to store this information into a data extension. This would avoid us unnecessary API Calls if the data has not changed. The data extension named AUTOMATIONS_SCHEDULES should look like this:
Field Name | Field Type | Length | Default Value |
---|---|---|---|
Name | Text | 500 | |
Schedule | Text | 500 | |
scheduleStatus | Text | 80 | |
scheduledTime | Date | ||
scheduleFrequency | Text | 255 | |
scheduleInterval | Text | 50 | |
scheduleDays | Text | 255 | |
scheduleHours | Text | 500 | |
description | Text | 500 | |
secret | Number | 1 |
The secret
column is a way to lookup all records of a data extension by using the condition secret = 1 on lookup functions.
Now, let’s have a look at the code:
try {
automations = HTTP.Get(url, headerNames, headerValues);
automationContent = Platform.Function.ParseJSON(Platform.Function.ParseJSON(Stringify(automations)).Content);
// loop through all results
if(automationContent.entry.length > 0 && automationContent.entry.length !== null) {
for (i = 0; i < automationContent.entry.length; i++) {
automationDescription = automationContent.entry[i].description;
/* Getting schedule */
if(Platform.Function.ParseJSON(automationContent.entry[i].id) !== "" && Platform.Function.ParseJSON(automationContent.entry[i].id) !== null) {
url = tenantURL + ".rest.marketingcloudapis.com/automation/v1/automations/" + Platform.Function.ParseJSON(automationContent.entry[i].id);
headerNames = ["Authorization"];
headerValues = ["Bearer " + accessToken];
try {
automation = HTTP.Get(url, headerNames, headerValues);
automationScheduleObject = Platform.Function.ParseJSON(Platform.Function.ParseJSON(Stringify(automation)).Content);
if(typeof automationScheduleObject.schedule.icalRecur !== "undefined" && automationScheduleObject.schedule.scheduleStatus === "active"){
// Split the schedule object to different columns
scheduleSplit = automationScheduleObject.schedule.icalRecur.split(';');
if(typeof scheduleSplit[0] !== 'undefined') scheduleFrequency = scheduleSplit[0];
if(typeof scheduleSplit[2] !== 'undefined') scheduleInterval = scheduleSplit[2];
/* When automation scheduled to run everyday, replace empty by Everyday */
scheduleDays = (typeof scheduleSplit[3] !== 'undefined' ? scheduleSplit[3].split("=")[1] : 'Everyday');
/* Get scheduletime on a Date object to manipulate hours */
var scheduleToDate = new Date(automationScheduleObject.schedule.scheduledTime);
var startingHour = scheduleToDate.getHours();
var startingMinutes = scheduleToDate.getMinutes();
scheduleFrequencySplitted = scheduleFrequency.split('=')[1];
/* When frequency is HOURLY, get all daily schedules by adding the interval to the scheduletime */
if(scheduleFrequencySplitted === 'HOURLY') {
var hoursInterval = scheduleInterval.split('=');
var initialInterval = parseInt(hoursInterval[1]);
var hoursNumber = 24/initialInterval;
hoursSchedule = "";
for(j = 0; j < hoursNumber; j++){
hoursSchedule += scheduleToDate.getHours() + ":" + startingMinutes +" / ";
scheduleToDate.setHours(scheduleToDate.getHours()+initialInterval);
}
} else {
hoursSchedule = scheduleToDate.getHours() + ":"+ startingMinutes +" / ";
}
/* Write rows to a Data Extension */
rows = Platform.Function.UpsertData("AUTOMATIONS_SCHEDULE",["Name"],[automationContent.entry[i].name]
,["schedule","scheduleStatus","scheduledTime","scheduleFrequency",
"scheduleInterval", "scheduleDays", "hoursSchedule"],
[automationScheduleObject.schedule.icalRecur,
automationScheduleObject.schedule.scheduleStatus,
automationScheduleObject.schedule.scheduledTime,
scheduleFrequency, scheduleInterval, scheduleDays,
hoursSchedule]);
}
} catch (e) {
e = Stringify(e).replace(/[\n\r]/g, '')
Write(e);
}
}
}
}
} catch (e) {
e = Stringify(e).replace(/[\n\r]/g, '')
Write(e);
}
PART IV: Using Lightning Design System in CloudPages
What is Lightning Design System?
The Salesforce Lightning Design System (SLDS) helps you build applications with the look and feel of Lightning Experience without writing a single line of CSS. SLDS is a CSS framework that gives you access to the icons, color palettes, and font that our developers use to create Lightning Experience.
What’s better than having Lightning design on a landing page inside Marketing Cloud? Your account’s users will have the feeling that the page is actually provided by SFMC, and not a custom developed CloudPage.
To use Lightning Design System inside our CloudPage, we need to download the resources zip available on this page. After unzipping the file, we need to copy the content of the file named:
salesforce-lightning-design-system.min.css
Then, paste it into a CSS resource landing page, grab it’s url and use it in our main CloudPage’s head:
<link rel="stylesheet" href="YOUR_CSS_PAGE_URL" />
After that, we will use the official documentation which is very well presented by Salesforce to chose tens of ways to present our data. I have chosen Data Tables and Page Headers. I will let you discover the other objects by yourself.
We are using the code below for the header as long with the Opportunity object’s icon.
<!-- Header Start -->
<div class="slds-m-around_large">
<div class="slds-page-header">
<div class="slds-page-header__row">
<div class="slds-page-header__col-title">
<div class="slds-media">
<div class="slds-media__figure">
<span class="slds-icon_container slds-icon-standard-opportunity" title="opportunity">
<svg class="slds-icon slds-page-header__icon" aria-hidden="true">
<use xlink:href="/assets/icons/standard-sprite/svg/symbols.svg#opportunity"></use>
</svg>
<span class="slds-assistive-text">Automation</span>
</span>
</div>
<div class="slds-media__body">
<div class="slds-page-header__name">
<div class="slds-page-header__name-title">
<h1>
<span class="slds-page-header__title slds-truncate">Automations Schedules Listing</span>
</h1>
</div>
</div>
<p class="slds-page-header__name-meta">All your automations schedules in one page</p>
</div>
</div>
</div>
</div>
</div>
<!-- Header End -->
Then, we create a table and its headers. The parameters slds-max-medium-table_stacked-horizontal
and slds-table_fixed-layout
are important to limit the table’s width and have columns content resized.
<table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_striped slds-table_col-bordered slds-max-medium-table_stacked-horizontal slds-table_fixed-layout">
<thead>
<tr class="slds-line-height_reset">
<th class="" scope="col">
<div class="slds-truncate" title="Automation Name">Automation Name</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Description">Description</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Status">Status</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Schedule Frequency">Schedule Frequency</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Schedule Days">Schedule Days</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Schedule Hours">Schedule Hours</div>
</th>
</tr>
</thead>
<tbody>
After that, we need to create a row and cell for each automation. We can do that using Write
function inside the automations loop presented before in the article. Title
attribute is important to show cells’ content on mouse hover when data is truncated.
Write('<tr class="slds-hint-parent"><th data-label="Automation Name" scope="row"><div class="slds-truncate" title='+automationContent.entry[i].name+'>'+ automationContent.entry[i].name +'</div></td>');
Write('<th data-label="Description" scope="row"><div class="slds-truncate" title="'+automationDescription+'" >'+ automationDescription +'</div></td>');
Write('<th data-label="Status" scope="row"><div class="slds-truncate" title="'+automationScheduleObject.schedule.scheduleStatus+'" >'+ automationScheduleObject.schedule.scheduleStatus +'</div></td>');
Write('<th data-label="Schedule Frequency" scope="row"><div class="slds-truncate" title="'+scheduleFrequencySplitted+'" >'+ scheduleFrequencySplitted +'</div></td>');
Write('<th data-label="Schedule Days" scope="row"><div class="slds-truncate" title="'+scheduleDays+'" >'+ scheduleDays +'</div></td>');
Write('<th data-label="Schedule Hours" scope="row"><div class="slds-truncate" title="'+hoursSchedule+'" >'+ hoursSchedule +'</div></td>');
Write('</tr>');
The page will look like this:
Final code
Please note that there is a lot of things to improve and to do differently to parse and show this data. I tried to focus on the essential and new parts, I’ll let you innovate and customize the code according to your needs.
For example, to avoid making all those API calls every time you access the CloudPage, you can create a Preferences data extension that you lookup at the beginning of the script. This data extension can contain parameters like automationCount, refreshData, automationStatus (to decide if you want to pull all automations, or only active ones),…
%%[
SET @MID = '50000XXXX'
SET @apiCreds = LOOKUP('REST_Credentials', 'apiCreds', 'MID', @MID)
SET @apiCreds = DecryptSymmetric(@apiCreds, 'aes', 'INT_PWD', @null, 'INT_SALT', @null, 'INT_IV', @null)
]%%
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="https://cloud.actu.lamutuellegenerale.fr/salesforce-lightning-design-system.min.css" />
<style>
html {background-color: white}
</style>
</head>
<body>
<!-- Header Start -->
<div class="slds-m-around_large">
<div class="slds-page-header">
<div class="slds-page-header__row">
<div class="slds-page-header__col-title">
<div class="slds-media">
<div class="slds-media__figure">
<span class="slds-icon_container slds-icon-standard-opportunity" title="opportunity">
<svg class="slds-icon slds-page-header__icon" aria-hidden="true">
<use xlink:href="/assets/icons/standard-sprite/svg/symbols.svg#opportunity"></use>
</svg>
<span class="slds-assistive-text">Automation</span>
</span>
</div>
<div class="slds-media__body">
<div class="slds-page-header__name">
<div class="slds-page-header__name-title">
<h1>
<span class="slds-page-header__title slds-truncate">Automations Schedules Listing</span>
</h1>
</div>
</div>
<p class="slds-page-header__name-meta">All your automations schedules in one page</p>
</div>
</div>
</div>
</div>
</div>
<!-- Header End -->
<!-- Table headers -->
<table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_striped slds-table_col-bordered slds-max-medium-table_stacked-horizontal slds-table_fixed-layout">
<thead>
<tr class="slds-line-height_reset">
<th class="" scope="col">
<div class="slds-truncate" title="Automation Name">Automation Name</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Description">Description</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Status">Status</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Schedule Frequency">Schedule Frequency</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Schedule Days">Schedule Days</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Schedule Hours">Schedule Hours</div>
</th>
</tr>
</thead>
<tbody>
<!-- SSJS Script start-->
<script runat="server">
Platform.Load("Core","1.1.1");
/* script execution options */
var clearDataExtension = false;
var automationsCount = 25;
/* Passing apiCreds value from AMPScript to SSJS */
var apiCreds = Variable.GetValue('@apiCreds');
var credentialObj = Platform.Function.ParseJSON(apiCreds);
var clientId = credentialObj.clientId;
var clientSecret = credentialObj.clientSecret;
var authURL = credentialObj.authURL;
var tenantURL = authURL.substring(0,36);
/* Authentification */
var payload = {
client_id: clientId,
client_secret: clientSecret,
grant_type: "client_credentials"
};
var url = authURL + '/v2/token';
var contentType = 'application/json';
var accessToken;
try {
var accessTokenRequest = HTTP.Post(url, contentType, Stringify(payload));
if(accessTokenRequest.StatusCode == 200) {
var tokenResponse = Platform.Function.ParseJSON(accessTokenRequest.Response[0]);
accessToken = tokenResponse.access_token;
}
} catch (error) {
Write(Stringify(error));
}
/* Clear data extension before populating new data */
if(clearDataExtension) var DERows = Platform.Function.DeleteData('AUTOMATIONS_SCHEDULE',['secret'],[1]);
/* Getting automations */
url = tenantURL + ".rest.marketingcloudapis.com/legacy/v1/beta/automations/automation/definition/?$top="+ automationsCount +"&$skip=0&$sort=lastRunTime%20desc";
var headerNames = ["Authorization"];
var headerValues = ["Bearer " + accessToken];
var automations, automationContent, automation, automationScheduleObject, automationDescription;
var rows, scheduleSplit, scheduleFrequency, endDate, scheduleInterval, scheduleDays, hoursSchedule, scheduleFrequencySplitted;
try {
automations = HTTP.Get(url, headerNames, headerValues);
automationContent = Platform.Function.ParseJSON(Platform.Function.ParseJSON(Stringify(automations)).Content);
// loop through all results
if(automationContent.entry.length > 0 && automationContent.entry.length !== null) {
for (i = 0; i < automationContent.entry.length; i++) {
automationDescription = automationContent.entry[i].description;
/* Getting schedule */
if(Platform.Function.ParseJSON(automationContent.entry[i].id) !== "" && Platform.Function.ParseJSON(automationContent.entry[i].id) !== null) {
url = tenantURL + ".rest.marketingcloudapis.com/automation/v1/automations/" + Platform.Function.ParseJSON(automationContent.entry[i].id);
headerNames = ["Authorization"];
headerValues = ["Bearer " + accessToken];
try {
automation = HTTP.Get(url, headerNames, headerValues);
automationScheduleObject = Platform.Function.ParseJSON(Platform.Function.ParseJSON(Stringify(automation)).Content);
if(typeof automationScheduleObject.schedule.icalRecur !== "undefined" && automationScheduleObject.schedule.scheduleStatus === "active"){
// Split the schedule object to different columns
scheduleSplit = automationScheduleObject.schedule.icalRecur.split(';');
if(typeof scheduleSplit[0] !== 'undefined') scheduleFrequency = scheduleSplit[0];
if(typeof scheduleSplit[2] !== 'undefined') scheduleInterval = scheduleSplit[2];
/* When automation scheduled to run everyday, replace empty by Everyday */
scheduleDays = (typeof scheduleSplit[3] !== 'undefined' ? scheduleSplit[3].split("=")[1] : 'Everyday');
/* Get scheduletime on a Date object to manipulate hours */
var scheduleToDate = new Date(automationScheduleObject.schedule.scheduledTime);
var startingHour = scheduleToDate.getHours();
var startingMinutes = scheduleToDate.getMinutes();
scheduleFrequencySplitted = scheduleFrequency.split('=')[1];
/* When frequency is HOURLY, get all daily schedules by adding the interval to the scheduletime */
if(scheduleFrequencySplitted === 'HOURLY') {
var hoursInterval = scheduleInterval.split('=');
var initialInterval = parseInt(hoursInterval[1]);
var hoursNumber = 24/initialInterval;
hoursSchedule = "";
for(j = 0; j < hoursNumber; j++){
hoursSchedule += scheduleToDate.getHours() + ":" + startingMinutes +" / ";
scheduleToDate.setHours(scheduleToDate.getHours()+initialInterval);
}
} else {
hoursSchedule = scheduleToDate.getHours() + ":"+ startingMinutes +" / ";
}
// Writing table elements
Write('<tr class="slds-hint-parent"><th data-label="Automation Name" scope="row"><div class="slds-truncate" title='+automationContent.entry[i].name+'>'+ automationContent.entry[i].name +'</div></td>');
Write('<th data-label="Description" scope="row"><div class="slds-truncate" title="'+automationDescription+'" >'+ automationDescription +'</div></td>');
Write('<th data-label="Status" scope="row"><div class="slds-truncate" title="'+automationScheduleObject.schedule.scheduleStatus+'" >'+ automationScheduleObject.schedule.scheduleStatus +'</div></td>');
Write('<th data-label="Schedule Frequency" scope="row"><div class="slds-truncate" title="'+scheduleFrequencySplitted+'" >'+ scheduleFrequencySplitted +'</div></td>');
Write('<th data-label="Schedule Days" scope="row"><div class="slds-truncate" title="'+scheduleDays+'" >'+ scheduleDays +'</div></td>');
Write('<th data-label="Schedule Hours" scope="row"><div class="slds-truncate" title="'+hoursSchedule+'" >'+ hoursSchedule +'</div></td>');
Write('</tr>');
/* Write rows to a Data Extension */
rows = Platform.Function.UpsertData("AUTOMATIONS_SCHEDULE",["Name"],[automationContent.entry[i].name]
,["schedule","scheduleStatus","scheduledTime","scheduleFrequency",
"scheduleInterval", "scheduleDays", "scheduleHours", "description"],
[automationScheduleObject.schedule.icalRecur,
automationScheduleObject.schedule.scheduleStatus,
automationScheduleObject.schedule.scheduledTime,
scheduleFrequency, scheduleInterval, scheduleDays,
hoursSchedule, automationDescription]);
}
} catch (e) {
e = Stringify(e).replace(/[\n\r]/g, '')
Write(e);
}
}
}
}
} catch (e) {
e = Stringify(e).replace(/[\n\r]/g, '')
Write(e);
}
</script>
</tbody>
</table>
</div>
</body>
</html>